package com.vhly.ddxdecompiler.dexreader.file.mixeditem;

import com.vhly.ddxdecompiler.dexreader.file.constant.*;
import com.vhly.ddxdecompiler.dexreader.io.DexInputStream;

import java.io.IOException;

/**
 * Created by IntelliJ IDEA.
 * User: vhly
 * Date: 2009-10-19
 * Time: 19:01:40
 *
 * @author vhly[FR]
 */
public final class ValueDecoder {

    private static final int VALUE_BYTE = 0;
    private static final int VALUE_SHORT = 2;
    private static final int VALUE_CHAR = 3;
    private static final int VALUE_INT = 4;
    private static final int VALUE_LONG = 6;
    private static final int VALUE_FLOAT = 16;
    private static final int VALUE_DOUBLE = 17;
    private static final int VALUE_STRING = 23;
    private static final int VALUE_TYPE = 24;
    private static final int VALUE_FIELD = 25;
    private static final int VALUE_METHOD = 26;
    private static final int VALUE_ENUM = 27;
    private static final int VALUE_ARRAY = 28;
    private static final int VALUE_ANNOTATION = 29;
    private static final int VALUE_NULL = 30;
    private static final int VALUE_BOOLEAN = 31;

    public Object[] readArray(DexInputStream din) throws IOException {
        Object[] ret = null;
        int size = din.readUnsignedLeb128();
        System.out.println("[Debug] VD readArray size = " + size);
        if (size > 0) {
            ret = new Object[size];
            for (int i = 0; i < size; i++) {
                ret[i] = readConstant(din);
            }
        }
        return ret;
    }

    private Object readConstant(DexInputStream din) throws IOException {
        int first = din.readByte();
        int type = first & 0x1f;
        int len = ((first >> 5) & 0x07) + 1;
        Object ret = null;
        int value;
        CstLiteralBits bit;
        long fv;
        switch (type) {
            case VALUE_BYTE:
            case VALUE_SHORT:
            case VALUE_INT:
            case VALUE_LONG:
                value = readSignedIntegralValue(din, len);
                bit = new CstLiteralBits();
                bit.setLiteralBits(value);
                ret = bit;
                break;
            case VALUE_CHAR:
                value = readUnsignedIntegralValue(din, len);
                bit = new CstLiteralBits();
                bit.setLiteralBits(value);
                ret = bit;
                break;
            case VALUE_FLOAT:
                fv = readRightZeroExtendedValue(din, len);
                float fl = Float.intBitsToFloat((int) (fv >> 32));
                ret = new CstFloat(fl);
                break;
            case VALUE_DOUBLE:
                fv = readRightZeroExtendedValue(din, len);
                double dl = Double.longBitsToDouble(fv);
                ret = new CstDouble(dl);
                break;
            case VALUE_STRING:
                value = readUnsignedIntegralValue(din, len); // string index
                ret = new CstString(value);
                break;
            case VALUE_TYPE:
                value = readUnsignedIntegralValue(din, len); // typeid index
                ret = new CstType(value);
                break;
            case VALUE_FIELD:
                value = readUnsignedIntegralValue(din, len); // field index
                ret = new CstField(value);
                break;
            case VALUE_METHOD:
                value = readUnsignedIntegralValue(din, len); // method index
                ret = new CstMethod(value);
                break;
            case VALUE_ENUM:
                value = readUnsignedIntegralValue(din, len); // enum -> field
                ret = new CstEnum(value);
                break;
            case VALUE_ARRAY:
                ret = readArray(din);
                break;
            case VALUE_ANNOTATION:
                ret = readAnnotation(din);
                break;
            case VALUE_NULL:
                break;
            case VALUE_BOOLEAN:
                int boo = len - 1;
                ret = (boo == 1 ? CstBoolean.TRUE : CstBoolean.FALSE);
                break;
        }
        return ret;
    }

    public Annotation readAnnotation(DexInputStream din) throws IOException {
        Annotation anno = new Annotation();
        anno.typeIndex = din.readUnsignedLeb128();
        int pairSize = din.readUnsignedLeb128();
        if (pairSize > 0) {
            NameValuePair pair;
            for (int i = 0; i < pairSize; i++) {
                pair = new NameValuePair();
                pair.nameIndex = din.readUnsignedLeb128();
                pair.value = readConstant(din);
                anno.pairs.add(pair);
            }
        }
        return anno;
    }

    private int readSignedIntegralValue(DexInputStream din, int len) throws IOException {
//        int requiredBits = 65 - Long.numberOfLeadingZeros(value ^ value >> 63);
//        int requiredBytes = requiredBits + 7 >> 3;
//        out.writeByte(type | requiredBytes - 1 << 5);
//        for (; requiredBytes > 0; requiredBytes--) {
//            out.writeByte((byte) (int) value);
//            value >>= 8;
//        }
        int ret = 0;
        int q;
        for (int i = 0; i < len; i++) {
            q = din.readByte();
            ret += ((q << (8 * i)));
        }
        return ret;
    }

    private int readUnsignedIntegralValue(DexInputStream din, int len) throws IOException {
//        int requiredBits = 64 - Long.numberOfLeadingZeros(value);
//        if (requiredBits == 0)
//            requiredBits = 1;
//        int requiredBytes = requiredBits + 7 >> 3;
//        out.writeByte(type | requiredBytes - 1 << 5);
//        for (; requiredBytes > 0; requiredBytes--) {
//            out.writeByte((byte) (int) value);
//            value >>= 8;
//        }
        int ret = 0;
        int q;
        for (int i = 0; i < len; i++) {
            q = din.readUnsignedByte();
            ret += ((q << (8 * i)));
        }
        return ret;
    }

    private long readRightZeroExtendedValue(DexInputStream din, int len) throws IOException {
        long ret = 0;
        int q;
        for (int i = 0; i < len; i++) {
            q = din.readByte();
            ret += (q << (8 * i));
        }
        ret <<= (64 - len * 8);
        return ret;
    }
}
